/**
 * 
 */
package de.cbf.cam.dsa.inventory.composites;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;

import de.cbf.cam.dsa.inventory.dialogs.DialogChooseInventoryPosition;
import de.cbf.cam.dsa.inventory.interfaces.IItemDragSource;
import de.cbf.cam.dsa.model.entities.OneNPC;
import de.cbf.cam.dsa.model.entities.items.Container;
import de.cbf.cam.dsa.model.entities.items.Item;
import de.cbf.cam.dsa.model.entities.items.transfers.ArmorPartTransfer;
import de.cbf.cam.dsa.model.entities.items.transfers.ContainerTransfer;
import de.cbf.cam.dsa.model.entities.items.transfers.ElixirTransfer;
import de.cbf.cam.dsa.model.entities.items.transfers.ITransfer;
import de.cbf.cam.dsa.model.entities.items.transfers.ItemTransfer;
import de.cbf.cam.dsa.model.entities.items.transfers.WeaponTransfer;

/**
 * @author Nebelritter
 *
 */
public class CompositeInventoryTable extends Composite implements IItemDragSource {
	private final String ALL ="Alle";
	
	private Text textFreeSearch;
	private Table tableInventory;
	private Combo comboType;
	private Combo comboPlace;
	private Group groupDetail;
	
	private boolean functional = false;
		
	private List<Item> items =new ArrayList<Item>();
	
	private SelectionListener 	functTypeSelect 	= new FunctionalityTypeSelection();
	private SelectionListener 	functPlaceSelect 	= new FunctionalityPlaceSelection();
	private ModifyListener		functFreeSearch		= new FunctionalitySearchModification();
	
	/**
	 * @param parent
	 * @param style
	 */
	public CompositeInventoryTable(Composite parent, int style) {
		super(parent, style);		
		
		GridLayout layout = new GridLayout(3,false);
		layout.marginWidth  = 0;
		layout.marginHeight = 0;
		setLayout(layout);
		
		tableInventory = new Table(this, SWT.BORDER|SWT.H_SCROLL|SWT.V_SCROLL|SWT.MULTI|SWT.FULL_SELECTION);
		tableInventory.setLayoutData(new GridData(SWT.FILL,SWT.FILL,true,true,3,10));
		tableInventory.setHeaderVisible(true);
		tableInventory.setLinesVisible(true);
		tableInventory.setDragDetect(true);
		tableInventory.addListener(SWT.MouseMove,new Listener() {
			@Override
			public void handleEvent(Event e) {
				Point coordinates = new Point(e.x,e.y);
				if(tableInventory.dragDetect(e)){
					TableItem tableItem = tableInventory.getItem(coordinates);
					if(tableItem != null){
						int index = tableInventory.indexOf(tableItem);
						tableInventory.select(index);
					}
				}				
			}
		});
		
			TableColumn column;
			column = new TableColumn(tableInventory, SWT.LEFT);
			column.setText("Gegenstand");
			column.setWidth(140);
			
			column = new TableColumn(tableInventory, SWT.LEFT);
			column.setText("Typ");
			column.setWidth(90);
			
			column = new TableColumn(tableInventory, SWT.LEFT);
			column.setText("Position");
			column.setWidth(70);
			
			column = new TableColumn(tableInventory, SWT.LEFT);
			column.setText("Gewicht");
			column.setWidth(60);
			
			column = new TableColumn(tableInventory, SWT.LEFT);
			column.setText("Beschreibung");
			column.setWidth(200);
			
		textFreeSearch = new Text(this, SWT.BORDER|SWT.SEARCH|SWT.MULTI|SWT.ICON_SEARCH);
		textFreeSearch.setLayoutData(new GridData(SWT.FILL,SWT.CENTER,true,false,1,1));
		textFreeSearch.setText("<Suchen>");		
		textFreeSearch.addModifyListener(new SearchModificationAdvancer());
		textFreeSearch.addFocusListener(new FocusAdapter() {
			@Override
			public void focusGained(FocusEvent e) {
				Text origin=(Text)e.widget;
				origin.setSelection(0);
			}						
		});
		
		comboType = new Combo(this, SWT.READ_ONLY);
		comboType.setLayoutData(new GridData(SWT.FILL,SWT.CENTER,true,false,1,1));
		comboType.addSelectionListener(new TypeSelectionAdvancer());
				
		comboPlace = new Combo(this, SWT.READ_ONLY);
		comboPlace.setLayoutData(new GridData(SWT.FILL,SWT.CENTER,true,false,1,1));
		comboPlace.addSelectionListener(new PlaceSelectionAdvancer());
		
		groupDetail = new Group(this, SWT.NORMAL);
		groupDetail.setLayoutData(new GridData(SWT.FILL,SWT.FILL,true,false,3,7));
		GridLayout layoutDetail = new GridLayout(1,false);
		layoutDetail.marginHeight 	= 0;
		layoutDetail.marginWidth	= 0;
		layoutDetail.marginBottom	= 0;
		layoutDetail.marginTop		= 0;
		layoutDetail.marginLeft		= 0;
		layoutDetail.marginRight	= 0;
		groupDetail.setLayout(layoutDetail);
		groupDetail.setText("Details");
	
		buildDragSource();
		buildDropTarget();
	}
	
	protected void buildDragSource(){
		Transfer[] types = new Transfer[] { 				
				  WeaponTransfer.getInstance()
				 ,ArmorPartTransfer.getInstance()
				 ,ElixirTransfer.getInstance()
				, ContainerTransfer.getInstance()
				, ItemTransfer.getInstance()
				, TextTransfer.getInstance()
		};
	    DragSource source = new DragSource(tableInventory, DND.DROP_LINK|DND.DROP_MOVE);
	    source.setTransfer(types);
	   
	    source.addDragListener(new InventoryDragSourceListener());	
	}
	  
	protected void  buildDropTarget(){		
		Transfer[] types = new Transfer[] { 				
				  WeaponTransfer.getInstance()
				, ArmorPartTransfer.getInstance()				
				, ContainerTransfer.getInstance()
				, ElixirTransfer.getInstance()
				, ItemTransfer.getInstance()
				, TextTransfer.getInstance()
		};
		
		DropTarget target = new DropTarget(tableInventory, DND.DROP_LINK|DND.DROP_MOVE|DND.DROP_DEFAULT);
	    target.setTransfer(types);
	    
	    target.addDropListener(new InventoryDropTargetListener());
	}
	
	public void fillTable(){
		tableInventory.removeAll();

		for(int i =0;i<items.size();i++){
			Item item = items.get(i);
			addItemToTable(item);
		}		
	}
	
	private void addItemToTable(Item item){
		//use current filter selections for items display 
		String filterType =comboType.getItem(comboType.getSelectionIndex());
		String filterPlace =comboPlace.getItem(comboPlace.getSelectionIndex());
		boolean correctType = filterType.equals(ALL)||item.getClass().getSimpleName().equals(filterType);
		boolean correctPlace = filterPlace.equals(ALL)||item.getWear().equals(filterPlace);
		if(correctPlace&&correctType){
			//add the item
			TableItem tableItem = new TableItem(tableInventory,SWT.CENTER);
			tableItem.setText(0,item.getName());
			tableItem.setText(1,item.getClass().getSimpleName());
			tableItem.setText(2,item.getWear());
			tableItem.setText(3,item.getWeight()+"");
			tableItem.setText(4,item.getDescription());
			
			if (item instanceof Container) {
				Container container = (Container) item;
				for(int i = 0;i<container.getItemsInside().size();i++){
					Item itemInside = container.getItemsInside().get(i);
					
					addItemToTable(itemInside);
				}
			}
			tableItem.setData(item);
		}	
	}
	
	public Item getItemByGUID(String guid) {
		for(int i= 0;i<items.size();i++ ){
			if(items.get(i).getGuid().equals(guid)){
				return items.get(i);
			}
		}
		return null;
	}
	
	public void setInventory(List<Item> inventory){
		this.items = inventory;
		List<String> types = new ArrayList<String>();
		List<String> places = new ArrayList<String>();
		//set according filter possibility's
		for(int i=0;i<inventory.size();i++){
			Item item = inventory.get(i);
			String classString =item.getClass().getSimpleName();
			if(!types.contains(classString)){
				types.add(classString);
			}
			if(!places.contains(item.getWear())){
				places.add(item.getWear());
			}
		}
		//add possibility for no filter
		types.add(0,ALL);
		places.add(0,ALL);
		String[] dummy = new String[0];
		comboType.setItems(types.toArray(dummy));
		comboType.select(0);
		dummy = new String[0];
		comboPlace.setItems(places.toArray(dummy));
		comboPlace.select(0);
		//fill the table
		fillTable();
	}

	/**
	 * @return the functional
	 */
	public boolean isFunctional() {
		return functional;
	}

	/**
	 * @param functional the functional to set
	 */
	public void setFunctional(boolean functional) {
		if(!this.functional&&functional){
			//switch ON functionality
			comboType.addSelectionListener(functTypeSelect);
			comboPlace.addSelectionListener(functPlaceSelect);
			textFreeSearch.addModifyListener(functFreeSearch);
		}
		if(this.functional&&!functional){
			//switch OFF functionality
			comboType.removeSelectionListener(functTypeSelect);
			comboPlace.removeSelectionListener(functPlaceSelect);
			textFreeSearch.removeModifyListener(functFreeSearch);
		}		
		this.functional = functional;
	}
	
	/**
	 * method to get the draw area for detail data which wants to be displayed
	 * @return
	 */
	public Group getGroupDetail() {
		return groupDetail;
	}
	
	private List<ActionListener> typeSelectionListeners = new ArrayList<ActionListener>();
	private class TypeSelectionAdvancer extends SelectionAdapter{
		@Override
		public void widgetSelected(SelectionEvent e) {
			ActionEvent event = new ActionEvent(CompositeInventoryTable.this,ActionEvent.ACTION_PERFORMED, "typeSelected");
			for (ActionListener listener : typeSelectionListeners) {
				listener.actionPerformed(event);
			}
		}		
	}
	private List<ActionListener> placeSelectionListeners = new ArrayList<ActionListener>();
	private class PlaceSelectionAdvancer extends SelectionAdapter{
		@Override
		public void widgetSelected(SelectionEvent e) {
			ActionEvent event = new ActionEvent(CompositeInventoryTable.this,ActionEvent.ACTION_PERFORMED, "placeSelected");
			for (ActionListener listener : placeSelectionListeners) {
				listener.actionPerformed(event);
			}
		}		
	}
	private List<ActionListener> searchModificationListeners = new ArrayList<ActionListener>();
	private class SearchModificationAdvancer implements ModifyListener{		
		@Override
		public void modifyText(ModifyEvent e) {
			ActionEvent event = new ActionEvent(CompositeInventoryTable.this,ActionEvent.ACTION_PERFORMED, "searchModified");
			for (ActionListener listener : searchModificationListeners) {
				listener.actionPerformed(event);
			}
		}		
	}
	
	private class FunctionalityTypeSelection extends SelectionAdapter{
		@Override
		public void widgetSelected(SelectionEvent e) {
			fillTable();
		}		
	}
	private class FunctionalityPlaceSelection extends SelectionAdapter{
		@Override
		public void widgetSelected(SelectionEvent e) {
			fillTable();
		}		
	}
	private class FunctionalitySearchModification implements ModifyListener{
		@Override
		public void modifyText(ModifyEvent e) {
			if (e.widget instanceof Text) {
				tableInventory.deselectAll();
				//find search text
				Text text = (Text) e.widget;
				String search = text.getText();
				if(search.length()>0){
					//no selection at no search string
					List<Integer> selectionIndices = new ArrayList<Integer>();
					for(int i = 0;i<tableInventory.getItemCount();i++){
						// for every table item get data Item
						Item item = (Item)tableInventory.getItem(i).getData();
						
						if(item.getName().contains(search)){
							//item name contains search string -> add to found
							selectionIndices.add(i);
						}
					}		
					//select all found indices
					for(int i = 0;i<selectionIndices.size();i++){
						tableInventory.select(selectionIndices.get(i));
					}
				}				
			}
		}		
	}
	private class InventoryDragSourceListener extends DragSourceAdapter{
		private Item dragAway = null;
		
		public void dragSetData(DragSourceEvent event) {			  
			// Get the selected items in the drag source
			DragSource ds = (DragSource) event.widget;
			Table table = (Table) ds.getControl();
			TableItem[] selection = table.getSelection();	        
			  
			//get guid of selected item which will be transferred 
			Item item = (Item)selection[0].getData();
			event.data = item;
		}

		@Override
		public void dragFinished(DragSourceEvent event) {				  
			if (event.detail == DND.DROP_MOVE){
				System.out.println("Drag ended in MOVE"); //delete item and all items in container
				boolean found = findAndRemoveItemInContainer(items, dragAway);
				if(!found){
					System.err.println("Dragged item cannot be removed from Inventory, seems to be not there");
				}
				dragAway = null;
				fillTable();
			}
			if (event.detail == DND.DROP_LINK){
				System.out.println("Drag ended in LINK"); //refill table for changed wear /or notified via listener
				fillTable();
			}				 
		}

		private boolean findAndRemoveItemInContainer(List<Item> items,Item item){
			boolean found = false;
			found = items.remove(item);
			if(found){
				return found;
			}
			for(int i = 0;i<items.size();i++){
				Item possibleContainer = items.get(i);
				if (possibleContainer instanceof Container) {
					Container container = (Container) possibleContainer;
					found = findAndRemoveItemInContainer(container.getItemsInside(), item);
					if(found){
						return found;
					}
				}
			}
			return found;
		}
		
		@Override
		public void dragStart(DragSourceEvent event) {
			DragSource ds = (DragSource) event.widget;
			Table table = (Table) ds.getControl();
			TableItem[] selection = table.getSelection();	        
			  
			//get item which will be transferred 
			Item item = (Item)selection[0].getData();
			dragAway = item;
			//only drag items, that are not "in use"			
			if(item.getWear().equals(OneNPC.Hand_Main)||item.getWear().equals(OneNPC.Hand_Second)||item.getWear().equals(OneNPC.Hands_Both)){
				event.doit = false;
			}
			if(item.getWear().equals(CompositeArmor.WEARPosition)){
				event.doit = false;;
			}
		}
		
	}
	
	private class InventoryDropTargetListener extends DropTargetAdapter{
		public void dragEnter(DropTargetEvent event) {		       	
			if (event.detail == DND.DROP_DEFAULT) {
				boolean operationAllowed = false;	
				if ((event.operations & DND.DROP_LINK) != 0) {
		            event.detail = DND.DROP_LINK;
		            operationAllowed = true;
	 	        } 
	   			if ((event.operations & DND.DROP_MOVE) != 0) {
			        event.detail = DND.DROP_MOVE;
			        	operationAllowed = true;
		 	    }		   			
   				if(!operationAllowed){
   					event.detail = DND.DROP_NONE;
   				}
	 	    }	
//			//set correct datatype 
			TransferData[] datatypes = event.dataTypes;
			Transfer[] types = ((DropTarget)event.widget).getTransfer();
			boolean found = false;
			for(int i =0;i<types.length;i++ ){				
				if (types[i] instanceof ITransfer) {
					ITransfer iTransfer = (ITransfer) types[i];//java to native is not visible on Transfer-> need interface to make visible
					for(int j = 0;j<datatypes.length;j++){
						event.currentDataType = datatypes[j];						
						try{
							Object object = iTransfer.nativeToJava(datatypes[j]);//is important to find out, what data is exactly in it
							if(object!=null){
								//transfer with correct datatype and data(item) ->set datatype as wanted and break loop							
								event.currentDataType= datatypes[j];				
								found = true;
								break;
							}else{
								//datatype not accepted
							}
						}catch(SWTException e){
							//datatype was correct, but item in data did not fit						
						}
					}
					if(found){
						break;
					}
				}				
			}
			if(!found){
				//not accepted datatype/data
				event.detail = DND.DROP_NONE;
			}
		}	    		
   	
		public void dragOver(DropTargetEvent event) {	
			
			event.feedback = DND.FEEDBACK_SCROLL;
			
			// calculate table coordinates from display
			Point pt = Display.getCurrent().map(null, tableInventory, event.x, event.y);
			//get entry in table that mouse hovers
			TableItem tableItem = tableInventory.getItem(pt);
			if(tableItem!=null){
				Item selectedItem = (Item)tableItem.getData();
				if (selectedItem instanceof Container) {
					event.feedback |= DND.FEEDBACK_SELECT;
				}			        				
			}
			
		}
     
		public void drop(DropTargetEvent event) {
			DropTarget target = (DropTarget)event.getSource();
	        Transfer[] transfers = target.getTransfer();
	        Item item = null;
	        for(int i = 0;i<transfers.length;i++ ){
	        	if(transfers[i].isSupportedType(event.currentDataType)){
	        		System.out.println("Supported transfer: "+transfers[i].toString()+" data: "+event.data+ " class: "+event.data.getClass().getSimpleName());
	        		item= (Item)event.data;
	        		break;
	        	}	        	
	        }
	        if(item == null ){
	        	//no accepted data (item)
	        	return;
	        }     
           
            if((event.detail&DND.DROP_MOVE)==DND.DROP_MOVE){
            	//want move operation -> check if item is inventory
            	String wear = askForWearPlace(item,event.x,event.y);
            	if(wear !=null){
            		item.setWear(wear);
            		items.add(item);   
            	}            	         	
            }
            
            if((event.detail&DND.DROP_LINK)==DND.DROP_LINK){
            	//want to perform LINK operation -> want inventory place of item changed
            	Item checked = getItemByGUID(item.getGuid());
            	if(checked!=null){
            		item = checked;
            		String wear = askForWearPlace(item,event.x,event.y);
                	item.setWear(wear);
            	}	        	
            }           
			fillTable();
		}
		
		@Override
		public void dragOperationChanged(DropTargetEvent event) {
			 if (event.detail == DND.DROP_DEFAULT) {
   			 boolean operationAllowed = false;
   			 if ((event.operations & DND.DROP_LINK) != 0) {
		             event.detail = DND.DROP_LINK;
		             operationAllowed = true;
	 	         } 
   			 if ((event.operations & DND.DROP_MOVE) != 0) {
		             event.detail = DND.DROP_MOVE;
		             operationAllowed = true;
	 	         }	    			
   			 if(!operationAllowed){
   				 event.detail = DND.DROP_NONE;
   			 }
	 	     }		      
		}	
		
		private String askForWearPlace(Item item,int xPosition,int yPosition) {
			Point pt = Display.getCurrent().map(null, tableInventory, xPosition, yPosition);
	         
			TableItem tableItem = tableInventory.getItem(pt);
			if(tableItem!=null){
				Item selectedItem = (Item)tableItem.getData();
				if (selectedItem instanceof Container) {//should not put container in itself
					//we selected a table item which represents a container, so we put item in
					Container container = (Container) selectedItem;		
					if(!container.equals(item)){ //should not put container in itself
						container.addItem(item);
						item.setWear(container.getName());
						return null;
					}
				}
			}
			//not sure where to put it (no table item, or non-container)-> open shell to ask
			//get possible positions
			String[] places = comboPlace.getItems();
			List<String> positions = new ArrayList<String>();
			for (int i = 0; i < places.length; i++) {
				positions.add(places[i]);
			}			
			//prepare dialog
			Shell shell = getShell();
			DialogChooseInventoryPosition dialog = new DialogChooseInventoryPosition(shell);
			dialog.create();
			dialog.setData(positions);
			//open dialog and get Result
			String place = dialog.openForResult();
						
			return place;
		}
	}
}
